package com.boarsoft.dao.sharding;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.springframework.lang.Nullable;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionStatus;

public class ShardedHibernateTxManager implements PlatformTransactionManager {
	protected SessionFactory sessionFactory;

	@Override
	public TransactionStatus getTransaction(@Nullable TransactionDefinition td) throws TransactionException {
		Session session = sessionFactory.getCurrentSession();
		if (session == null) {
			session = sessionFactory.openSession();
		}
		Transaction tx = null;
		if (session.isJoinedToTransaction()) {
			tx = session.getTransaction();
		}
		switch (td.getPropagationBehavior()) {
		case TransactionDefinition.PROPAGATION_REQUIRED:
			// 如果当前没有事务，就新建一个事务，如果已经存在一个事务中，加入到这个事务中。这是最常见的选择。
			if (tx == null) {
				tx = session.beginTransaction();
				return new DefaultTransactionStatus(//
						tx, true, true, td.isReadOnly(), false, null);
			}
			return new DefaultTransactionStatus(//
					tx, false, true, td.isReadOnly(), false, null);
		case TransactionDefinition.PROPAGATION_SUPPORTS:
			// 支持当前事务，如果当前没有事务，就以非事务方式执行。
			return new DefaultTransactionStatus(tx, false, true, td.isReadOnly(), false, null);
		case TransactionDefinition.PROPAGATION_MANDATORY:
			// 使用当前的事务，如果当前没有事务，就抛出异常。
			if (tx == null) {
				throw new IllegalStateException("Current session has no joined transaction");
			}
			return new DefaultTransactionStatus(tx, false, true, td.isReadOnly(), false, null);
		case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
			// 新建事务，如果当前存在事务，把当前事务挂起。
			Session ss = sessionFactory.openSession();
			tx = ss.beginTransaction();
			return new DefaultTransactionStatus(tx, true, true, td.isReadOnly(), false, ss);
		case TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
			// 以非事务方式执行操作，如果当前存在事务，就把当前事务挂起。
			return new DefaultTransactionStatus(null, false, true, td.isReadOnly(), false, null);
		case TransactionDefinition.PROPAGATION_NEVER:
			// 以非事务方式执行，如果当前存在事务，则抛出异常。
			if (tx == null) {
				return new DefaultTransactionStatus(null, false, true, td.isReadOnly(), false, null);
			}
			throw new IllegalStateException("Transaction is no allowed");
		case TransactionDefinition.PROPAGATION_NESTED:
			// 如果当前存在事务，则在嵌套事务内执行。如果当前没有事务，则执行与PROPAGATION_REQUIRED类似的操作。
			return new DefaultTransactionStatus(//
					session.beginTransaction(), tx == null, true, td.isReadOnly(), false, null);
		}
		throw new IllegalStateException("Invalid propagation config");
	}

	@Override
	public void commit(TransactionStatus status) throws TransactionException {
		DefaultTransactionStatus ts = (DefaultTransactionStatus) status;
		Transaction tx = (Transaction) ts.getTransaction();
		if (tx == null) {
			return;
		}
		if (tx.isActive()) {
			tx.commit();
			return;
		}
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException("Transaction is completed");
		}
	}

	@Override
	public void rollback(TransactionStatus status) throws TransactionException {
		DefaultTransactionStatus ts = (DefaultTransactionStatus) status;
		Transaction tx = (Transaction) ts.getTransaction();
		if (tx == null) {
			return;
		}
		if (tx.isActive()) {
			tx.rollback();
			return;
		}
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException("Transaction is completed");
		}
	}

	protected Session getSession() {
		try {
			return sessionFactory.getCurrentSession();
		} catch (Exception e) {
			return sessionFactory.openSession();
		}
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public SessionFactory getSessionFactory() {
		return this.sessionFactory;
	}

}